package com.czg.jsonschema2lombokpojo.rules;

import java.util.LinkedHashMap;
import java.util.Map;

import com.czg.jsonschema2lombokpojo.Schema;

import com.fasterxml.jackson.databind.JsonNode;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JType;

/**
 * 应用“additionalProperties” JSONschema规则。
 *
 * @see <a href=
 *      "http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.6">http:/
 *      /tools.ietf.org/html/draft-zyp-json-schema-03#section-5.6</a>
 */
public class AdditionalPropertiesRule implements Rule<JDefinedClass, JDefinedClass> {

    private final RuleFactory ruleFactory;

    protected AdditionalPropertiesRule(RuleFactory ruleFactory) {
        this.ruleFactory = ruleFactory;
    }

    /**
     *应用此schema规则执行所需的代码生成步骤。
     * < p >
     *如果additionalProperties指定并设置为布尔值 <code>false</code>，此规则不会对生成的代码做任何更改 Java类型（该类型不允许附加属性）。
     * < p >
     *如果additionalProperties节点为<code>null</code>(未在schema)或为空，则新的bean属性命名为“additionalProperties”，类型为{@link Map}{@literal <String,Object>}
     *添加到生成的类型（使用适当的访问器）。的访问器被注解以允许在JSON中发现未识别的（额外的）属性从/到此映射要编组/反编组的数据。
     * < p >
     *如果additionalProperties节点存在并且指定了一个schema，那么一个“additionalProperties”映射被添加到生成的类型。这一次
     *映射值将被限制，必须是一个新的实例生成的Java类型，该类型将基于 additionalPropertiesschema提供。
     * 如果schema没有指定javaType属性，将派生新生成的类型的名称从nodeName和后缀“Property”。
     *
     * @param nodeName    附加属性所对应的schema节点的名称节点适用
     * @param node 附加属性节点本身，在schema（可能）中找到，如果在schema中没有指定，则为null)
     * @param jclass 为表示该schema而生成的Java类型
     * @return 给定的Java类型jclass
     */


    @Override
    public JDefinedClass apply(String nodeName, JsonNode node, JsonNode parent, JDefinedClass jclass, Schema schema) {

        if (node != null && node.isBoolean() && node.asBoolean() == false) {
            // no additional properties allowed
            return jclass;
        }

        if (!this.ruleFactory.getGenerationConfig().isIncludeAdditionalProperties()) {
            // no additional properties allowed
            return jclass;
        }

        if (!ruleFactory.getAnnotator().isAdditionalPropertiesSupported()) {
            // schema allows additional properties, but serializer library can't support them
            return jclass;
        }

        JType propertyType;
        if (node != null && node.size() != 0) {
            String pathToAdditionalProperties;
            if (schema.getId() == null || schema.getId().getFragment() == null) {
                pathToAdditionalProperties = "#/additionalProperties";
            } else {
                pathToAdditionalProperties = "#" + schema.getId().getFragment() + "/additionalProperties";
            }
            Schema additionalPropertiesSchema = ruleFactory.getSchemaStore().create(schema, pathToAdditionalProperties, ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters());
            propertyType = ruleFactory.getSchemaRule().apply(nodeName + "Property", node, parent, jclass, additionalPropertiesSchema);
            additionalPropertiesSchema.setJavaTypeIfEmpty(propertyType);
        } else {
            propertyType = jclass.owner().ref(Object.class);
        }
        addAdditionalPropertiesField(jclass, propertyType);
        return jclass;
    }

    private JFieldVar addAdditionalPropertiesField(JDefinedClass jclass, JType propertyType) {
        JClass propertiesMapType = jclass.owner().ref(Map.class);
        propertiesMapType = propertiesMapType.narrow(jclass.owner().ref(String.class), propertyType.boxify());

        JClass propertiesMapImplType = jclass.owner().ref(LinkedHashMap.class);
        propertiesMapImplType = propertiesMapImplType.narrow(jclass.owner().ref(String.class), propertyType.boxify());

        JFieldVar field = jclass.field(JMod.PRIVATE, propertiesMapType, "additionalProperties");

        field.init(JExpr._new(propertiesMapImplType));

        return field;
    }

    private String getBuilderClassName(JDefinedClass c) {
        return ruleFactory.getNameHelper().getBaseBuilderClassName(c);
    }

}
